/*-----------------------------------------------------------------------
 * MediaFrame is an Open Source streaming media platform in Java 
 * which provides a fast, easy to implement and extremely small applet 
 * that enables to view your audio/video content without having 
 * to rely on external player applications or bulky plug-ins.
 *
 *-----------------------------------------------------------------------
 *
 * We changed a lot of code and added a lot of functionality.
 * This includes, but not limited to, the following changes:
 * 1. The project was renamed to MediaFrame;
 * 2. The connection speed detection procedure was added;
 * 3. The JavaScript API functions were added;
 * 4. The pre and post image support functionality was added;
 * 5. The ability to save movie into the local disk was added;
 * 6. The inner buffer for a movie file was added;
 * 7. The click-through functionality was added;    
 * 8. The .zip files support was added;    
 * 9. The realtime feedback agent functionality was added.    
 * For the full list of the current functionality please visit 
 * the following web page: http://mediaframe.org/
 *    
 * 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev
 *
 *-----------------------------------------------------------------------
 *
 *  Added some very experimental stretch-code... UGLY, but optional...
 *
 *  -- Ron -- (29-04-2002) r_breukelaar@hotmail.com
 *
/*-----------------------------------------------------------------------
 * 29/01/00		Initial version. mdm@techie.com
/*-----------------------------------------------------------------------
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 */

package mediaframe.mpeg1.audio.player;

import java.io.*;
import java.applet.Applet;
import mediaframe.mpeg1.audio.decoder.*;
import mediaframe.mpeg1.*;

/**
 * The <code>Player</code> class implements a simple player for playback
 * of an MPEG audio stream. 
 * 
 * @author	Mat McGowan
 * @since	0.0.8
 */

public class Player implements Runnable
{	  	
	/**
	 * The MPEG audio bitstream. 
	 */
	/*final*/ private io_tool		bitstream;
	
	/**
	 * The MPEG audio decoder. 
	 */
	/*final*/ private Decoder		decoder; 
	
	/**
	 * The <code>AudioDevice</code> the audio samples are written to. 
	 */
	private AudioDevice	audio;
	
	/** The Audio header of the MPEG stream. */
	private final Header			header = new Header();
	/** The 16-Bit CRC checksum of the MPEG audio bitstream. */
	private Crc16[]				crc    = new Crc16[1];
	
	/** The "Audio Decoder" thread object. */
	private volatile Thread audio_thread; 

	/** The reference to the video applet. */
	private MPEG1 _pech;
	/** <tt>True</tt>, if the audio player is stopped. */
	private boolean _stopped = false;
	/** <tt>True</tt>, if the audio device detection has been already done. */
	private static boolean _soundChecked = false;
	/** 
	 * <tt>True</tt>, if the player has detected the suitable audio device for the current VM 
	 * and able to play the sound. 
	 */
	private static boolean _soundEnabled = false;

	/**
	 * Creates a new <code>Player</code> instance with the specified MPEG audio bitstream and 
	 * the reference to the video applet.
	 * @param stream the MPEG audio bitstream.
	 * @param pl the reference to the video applet.
	 */
	public Player(io_tool stream, MPEG1 pl)
	{
		bitstream = stream;
		decoder = new Decoder();
		_pech = pl;

		try {
			AudioDeviceFactory r = AudioDeviceFactory.loadDriverClass();
			audio = r.createAudioDevice();
			if(audio == null)
				throw new JavaLayerException("The suitable driver is absent!");
			audio.open(decoder);
			_soundEnabled = true;
		} catch (JavaLayerException ex) {
			System.err.println("Problem playing audio: " + ex);
			_stopped = true;
		}
		
		_soundChecked = true;

		// Tell decoder to downconvert?
		if (audio!=null && audio.isEightBitEightKhzMuLaw()) {
			decoder.getParams().setDownconvert();
		}
	}

	/**
	 * Starts the "Audio Decoder" thread.
	 */
	public void start() {
		audio_thread = new Thread(this, "Audio Decoder");
		audio_thread.start();
	}

	/**
	 * Decodes the MPEG audio bitstream (calls in the loop the {@link Player#decodeFrame decodeFrame()} method for each audio frame).
	 * This method is called by the JRE after the "Audio Decoder" thread is started. 
	 */
	public void run() {
		Thread thisThread = Thread.currentThread();
		if(!"Audio Decoder".equals(thisThread.getName()))
			return;
		try {

			while (thisThread == audio_thread && decodeFrame());
			// Last frame, ensure all data flushed to the audio device. 
			AudioDevice out = audio;
			if (out != null) out.flush();
		} catch (JavaLayerException ex) {
			System.err.println("Problem playing audio: " + ex);
			_stopped = true;
			ex.printStackTrace(System.err);
		} catch (InterruptedException ex) {
			System.out.println("audio.player.Player() stopped -- exiting run()");
			_stopped = true;
		} catch (Exception e) {
			System.out.println("hmmm... AUDIO is trying to do something illegal..."+e);
			_stopped = true;
		}
		
		System.out.println("Audio process is closed!");

		if (audio != null)
		{
			audio.close();
			audio = null;
		}

		audio_thread = moribund = null;
		
	}
	
	/**
	 * Turns on audio output device.
	 */
	public void play() {
		try
		{
			if(audio != null)
				audio.play();
			else
				System.out.println("Player:play(): cannot find the audio device!");
		}
		catch(Exception e)
		{
			System.out.println("Ploblems playing audio -> no audio...");
			//System.out.println(e);

			if (audio_thread != null)
			{
				try
				{
					audio_thread.interrupt();
				}
				catch(Throwable e2)
				{
					System.out.println("Unable to kill audio-thread...");
				}
			}
			
			audio = null;
			_stopped = true;
		}
	}


	/** The "Audio Decoder" thread that is being stopped. */
	private Thread moribund = null;

	/**
	 * Closes this player.  
	 * Any audio currently playing is stopped immediately. 
	 */
	public void stop() {
		
		if(_stopped)
			return;

		System.out.println("Try to stop Audio process...");
		_stopped = true;

		moribund = audio_thread;
		audio_thread = null;

		if(audio != null) {
			audio.pause();
			try {
				Thread.sleep(10);
			} catch (Exception ex) {}
			if(audio != null)
				audio.close();
		} else
			System.out.println("Player:stop(): cannot find the audio device!");

		if (audio_thread != null)
		{
			moribund = audio_thread;
			audio_thread = null;
			if(moribund != null) {
				try {
					moribund.interrupt();
				} catch (Throwable tr) {}
			}
		}
	}

	/** 
	 * Waits until the audio process is dead.
	 */
	public synchronized void join(){
			if(moribund != null)
			try { 
				moribund.join(1000); 
			} catch (Throwable tr) { 
				System.out.println("strange---interrupted"); 
			}
	}

	
	/**
	 * Pause the Audio Player.
	 */
	public void pause(){
		if(audio != null)
			audio.pause();
		else
			System.out.println("Player:pause(): cannot find the audio device!");		
	}
	
	/**
	 * Decodes a single frame.
	 * 
	 * @return true if there are no more frames to decode, false otherwise.
	 * 
	 * @throws JavaLayerException if the decoder could not decode the audio frame.
	 * @throws InterruptedException if another process interrupts the current process.
	 */
	protected boolean decodeFrame() throws JavaLayerException, InterruptedException {

		if (audio == null || audio_thread == null) return false;
		
		if(! bitstream.is_eof()) {
			Obuffer output = null;

			try
			{
				header.read_header(bitstream, crc);

				// sample buffer set when decoder constructed
				output = decoder.decodeFrame(header, bitstream);
			}
			catch(EOFException e)
			{
				synchronized (this) {
					AudioDevice out = audio;
					if (out != null) {
						out.endOfFile();
					}
				}
				
				System.out.println("Audio EOF!");
				return true;
			}			catch(Exception e)
			{
				System.out.println("hmmm... audio corrupt...:" + e);
				e.printStackTrace(System.out);
			}

			synchronized (this) {
				AudioDevice out = audio;
				if (out != null && output != null) {
					out.write(output.getBuffer(), 0, output.getBufferLength());
				}
			}
		
		} else {
			if(audio_thread != null) {
			    Thread.sleep(1000);
			}
		}

		if(audio_thread != null)
			Thread.yield();

		return true;
	}

/*
	public double getPosition()
	{
		if (audio == null)
			return -1;
		else
			return audio.getPosition();
	}

	public double getBufferPosition()
	{
		if (audio == null)
			return -1;
		else
			return audio.getBufferPosition();
	}
*/
	/**
	 * Returns <tt>true</tt>, if the audio playback is stopped.
	 */
	public boolean isStopped()
	{
		return _stopped;
	}

	/**
	 * Sets the mute state of the audio player.
	 * @param mute the mute state (<tt>true</tt>, if the mute on and the player don't play the audio). 
	 */
	public void setMute(boolean mute){
		if(audio != null){
			audio.setMute(mute);
		}
	}
	
	/**
	 * Sets the volume (in the range 0..1) of the audio player. 
	 * @param volume the volume to set.
	 */
	public void setVolume(float volume){
		if(audio != null){
			audio.setVolume(volume);
		}
	}

	/** 
	 * Returns <tt>true</tt>, if the player has detected the suitable audio device for the current VM 
	 * and able to play the sound. 
	 */
	public static boolean isSoundEnabled(){
		if(! _soundChecked) {
			try {
				AudioDeviceFactory r = AudioDeviceFactory.loadDriverClass();
				AudioDevice audio = r.createAudioDevice();
				if(audio != null) {
					audio.close();
					_soundEnabled = true;
				}
			} catch (Throwable tr) {}
		}
		_soundChecked = true;
		return _soundEnabled;
	}
	
}
